﻿using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

namespace GameToolkit.UI
{
    public interface INavigateBlocker
    {
        bool CanMoveNext(SelectableNavigation navigation, GameObject current, MoveDirection direct);
    }

    public interface INavigateHandler : IEventSystemHandler
    {
        bool OnMoveNext(MoveDirection direct);
    }

    public struct SelectableRect
    {
        public bool isVaild;
        public Rect rect;
        public SelectableRect(Rect rect)
        {
            isVaild = true;
            this.rect = rect;
        }
    }

    public interface ISelectableRectModifier
    {
        //bool SelectDefault();
        SelectableRect GetSelectableRect(GameObject selectable, Transform asParent);
    }
    public interface ISelectableByIndex
    {
        void Select(int index);
    }

    public interface ISelectableByData
    {
        void Select(object data);
    }


    public class SelectableNavigation : MonoBehaviour
    {
        static HashSet<int> sSelecting = new HashSet<int>();
        static uint sSelectRefs;
        static void TrackSelectableBegin()
        {
            sSelectRefs++;
            if (sSelectRefs == 1)
                sSelecting.Clear();
        }

        static void TrackSelectableEnd()
        {
            sSelectRefs--;
            if (sSelectRefs == 0)
                sSelecting.Clear();
        }

        public enum ENavigationType
        {
            Horizontal,
            Vertical,
            Grid,
            Escape,
        }

        public enum ESelectType
        {
            Auto,
            Selectable,
            ToggleGroup,
        }

        public enum EWrapSide
        {
            Left = 1 << MoveDirection.Left,
            Right = 1 << MoveDirection.Right,
            Up = 1 << MoveDirection.Up,
            Down = 1 << MoveDirection.Down,
        }

        public static Rect GetLocalRect(Transform parent, Transform local, Rect rect)
        {
            if (parent == local)
                return rect;
            var matrix = parent.worldToLocalMatrix * local.localToWorldMatrix;
            var min = matrix.MultiplyPoint(rect.min);
            var max = matrix.MultiplyPoint(rect.max);
            var xmin = Mathf.Min(min.x, max.x);
            var ymin = Mathf.Min(min.y, max.y);
            var xmax = Mathf.Max(min.x, max.x);
            var ymax = Mathf.Max(min.y, max.y);
            return new Rect(xmin, ymin, xmax - xmin, ymax - ymin);
        }

#pragma warning disable CS0649

        [SerializeField]
        ESelectType m_SelectType;
        public ESelectType SelectType
        {
            get { return m_SelectType; }
            set
            {
                if (m_SelectType != value)
                {
                    var tog = m_SelectType == ESelectType.ToggleGroup;
                    var tog2 = value == ESelectType.ToggleGroup;
                    m_SelectType = value;
                    if (mGroup != null && (tog ^ tog2))
                    {
                        mGroup.SetDirty();
                    }
                }
            }
        }

        //bool m_ToggleGroupMode;

        [SerializeField]
        RectTransform m_LocateTransform;

        [SerializeField]
        ENavigationType m_NavigationType;

        [SerializeField, Mask(typeof(EWrapSide), ToggleColumns = 4)]
        int m_WrapSides;

        // 计算宫格距离是，同轴上的最小单元格单位大小（百分比）
        [SerializeField, Range(0.01f, 1f)]
        float m_GridThreshold = 0.25f;

        // 计算宫格距离时，不同轴上的距离倍率
        [SerializeField]
        float m_GridDistanceScale = 1f;

        [SerializeField]
        float m_FailedVibrate = 0.3f;

        [SerializeField]
        bool m_ReselectBetweenGroup;

#pragma warning restore
        ISelectableRectModifier mSelectableHandler;
        SelectableGroup mGroup;
        INavigateBlocker mBlocker;

        public event System.Action<Selectable> OnSelectableChagned;

        public EWrapSide WrapSides
        {
            get { return (EWrapSide)m_WrapSides; }
            set
            {
                if (m_WrapSides != (int)value)
                {
                    m_WrapSides = (int)value;
                }
            }
        }

        private void Awake()
        {
            mSelectableHandler = GetComponent<ISelectableRectModifier>();
        }

        public void RebuildGroup()
        {
            if (mGroup != null)
                mGroup.SetDirty();
        }

        private void OnTransformChildrenChanged()
        {
            if (mGroup != null)
                mGroup.SetDirty();
        }

        MyEventSystem mEvSys;


        private void OnEnable()
        {
            if (m_NavigationType != ENavigationType.Escape)
            {
                mBlocker = GetComponentInParent<INavigateBlocker>();
                mEvSys = EventSystem.current as MyEventSystem;
                if (mEvSys != null)
                    mEvSys.OnSelectionChanged += NotifySelected;
            }
        }

        private void OnDisable()
        {
            if (mEvSys != null)
                mEvSys.OnSelectionChanged -= NotifySelected;
            mEvSys = null;
            if (mGroup != null && m_ReselectBetweenGroup)
            {
                mGroup.SetFinalSelected(null);
            }
        }

        private void OnDestroy()
        {
            OnSelectableChagned = null;
        }

        public void SelectLeft()
        {
            SelectNext(MoveDirection.Left);
            //MainThread.RunOnMainThread(SelectNext, MoveDirection.Left);
        }

        public void SelectRight()
        {
            SelectNext(MoveDirection.Right);
            //MainThread.RunOnMainThread(SelectNext, MoveDirection.Right);
        }

        public void SelectUp()
        {
            SelectNext(MoveDirection.Up);
            //MainThread.RunOnMainThread(SelectNext, MoveDirection.Up);
        }

        public void SelectDown()
        {
            SelectNext(MoveDirection.Down);
            //MainThread.RunOnMainThread(SelectNext, MoveDirection.Down);
        }

        public void SelectNext(MoveDirection dir)
        {
            var grp = GetSelectableGroup();
            var trans = transform.parent;
            while (grp == null && trans != null)
            {
                var p = trans.GetComponentInParent<SelectableNavigation>();
                if (p == null)
                    break;
                grp = p.GetSelectableGroup();
                trans = p.transform.parent;
            }
            if (grp != null)
            {
                var item = EventSystem.current.currentSelectedGameObject;
                var src = item == null ? null : item.transform as RectTransform;
                if (src != null && !src.IsChildOf(transform))
                    src = null;
                grp.SelectNext(src, dir);
            }
        }

        public bool SelectNext(RectTransform rect, MoveDirection dir)
        {
            var grp = GetSelectableGroup();
            var trans = transform.parent;
            while (grp == null && trans != null)
            {
                var p = trans.GetComponentInParent<SelectableNavigation>();
                if (p == null)
                    break;
                grp = p.GetSelectableGroup();
                trans = p.transform.parent;
            }
            if (grp != null)
            {
                return grp.SelectNext(rect, dir);
            }
            return false;
        }

        public void SelectDefault()
        {
            SelectDefault(null);
        }

        public void SelectDefault(RectTransform wanted)
        {
            var grp = GetSelectableGroup();
            if (grp != null)
            {
                grp.Select(this, wanted);
            }
        }

        public void GetFinalSelected()
        {
            var grp = GetSelectableGroup();
            if (grp == null)
                return;
            grp.GetFinalSelected();
        }

        public void SetFinalSelected(GameObject target)
        {
            var grp = GetSelectableGroup();
            if (grp == null)
                return;
            grp.SetFinalSelected(target);
        }

        public GameObject GetSelected()
        {
            var ev = EventSystem.current;
            var current = ev == null ? null : ev.currentSelectedGameObject;
            var grp = GetSelectableGroup();
            if (grp != null)
                return grp.GetSelected(current == null ? null : current.transform as RectTransform);
            else
                return null;
        }

        public GameObject GetToggleOn()
        {
            var grp = GetSelectableGroup();
            if (grp != null)
                return grp.GetToggleOn();
            else
                return null;
        }

        void NotifySelected(Selectable sel)
        {
            //lastSelectedGameObject = sel.gameObject;
            var use = sel != null && sel.transform.IsChildOf(transform);
            //if (sel != null && sel.transform.IsChildOf(transform))
            OnSelectableChagned?.Invoke(use ? sel : null);
            //var p = transform.parent;
            //if(p != null)
            //{
            //    var grp = p.GetComponentInParent<SelectableNavigation>();
            //    if (grp != null)
            //        grp.NotifySelected(sel);
            //}
        }

        SelectableGroup GetSelectableGroup()
        {
            if (mGroup == null && m_NavigationType != ENavigationType.Escape)
            {
                if (m_NavigationType == ENavigationType.Horizontal)
                    mGroup = new HorizontalGroup(this);
                else if (m_NavigationType == ENavigationType.Vertical)
                    mGroup = new VerticalGroup(this);
                else if (m_NavigationType == ENavigationType.Grid)
                    mGroup = new SelectableGroup(this);
            }
            return mGroup;
        }

#if UNITY_EDITOR
        private void OnValidate()
        {
            mGroup = null;
            mSelectableHandler = GetComponent<ISelectableRectModifier>();
            var grps = GetComponentsInParent<SelectableNavigation>();
            if (grps != null)
            {
                foreach (var grp in grps)
                {
                    grp.mGroup = null;
                }
            }
        }
#endif
        static Queue<Transform> sCache = new Queue<Transform>();
        //public static GameObject lastSelectedGameObject;

        internal abstract class SelectableBase
        {
            protected RectTransform mRect;
            public abstract int InstanceId { get; }
            public RectTransform rectTransform { get { return mRect; } }

            public SelectableBase(RectTransform rect)
            {
                mRect = rect;
            }
            public abstract bool IsSelectable(SelectableNavigation grp);
            public abstract bool IsToggleOn();
            public abstract bool IsSelected(SelectableNavigation grp, RectTransform current);
            public abstract bool Select(SelectableNavigation grp, RectTransform src);
            public abstract Rect GetLocalRect(Transform parent);
        }

        internal class SelectableTarget : SelectableBase
        {
            //ESelectType mType;
            readonly int id;
            Selectable mSelectable;
            SelectableRect mSelectableRect;
            Transform mRectSpace;
            public override int InstanceId => id;

            public SelectableTarget(Selectable selectable) : base(selectable.transform as RectTransform)
            {
                mSelectable = selectable;
                id = selectable.GetInstanceID();
                //mType = type;
            }

            public override Rect GetLocalRect(Transform parent)
            {
                if (mSelectableRect.isVaild)
                    return SelectableNavigation.GetLocalRect(parent, mRectSpace, mSelectableRect.rect);
                else
                    return SelectableNavigation.GetLocalRect(parent, mRect, mRect.rect);
            }

            public override bool IsToggleOn()
            {
                if (!mSelectable.gameObject.activeInHierarchy)
                    return false;
                if (mSelectable is IToggle)
                    return ((IToggle)mSelectable).IsOn;
                if (mSelectable is Toggle)
                    return ((Toggle)mSelectable).isOn;
                return false;
            }

            public override bool IsSelected(SelectableNavigation grp, RectTransform current)
            {
                if (!mSelectable.gameObject.activeInHierarchy)
                    return false;
                if (grp.m_SelectType == ESelectType.ToggleGroup || grp.m_SelectType == ESelectType.Auto)
                {
                    var tog1 = mSelectable as IToggle;
                    if (tog1 != null)
                        return tog1.IsOn;
                    var tog2 = mSelectable as Toggle;
                    if (tog2 != null)
                        return tog2.isOn;
                    return grp.m_SelectType != ESelectType.ToggleGroup && mRect == current;
                }
                else
                {
                    return mRect == current;
                }
            }

            public override bool IsSelectable(SelectableNavigation grp)
            {
                if (mSelectable != null && mSelectable.interactable && mSelectable.isActiveAndEnabled)
                {
                    if (grp != null && grp.mSelectableHandler != null)
                    {
                        mRectSpace = grp.transform;
                        mSelectableRect = grp.mSelectableHandler.GetSelectableRect(mSelectable.gameObject, mRectSpace);
                        return mSelectableRect.isVaild;
                    }
                    return true;
                }
                return false;
            }

            public override bool Select(SelectableNavigation grp, RectTransform src)
            {
                if (grp.m_SelectType != ESelectType.Selectable)
                {
                    var tog = mSelectable as IToggle;
                    if (tog != null)
                    {
                        tog.IsOn = true;
                    }
                    else
                    {
                        var tog2 = mSelectable as Toggle;
                        if (tog2 != null)
                            tog2.isOn = true;
                    }
                }
                if (grp.m_SelectType != ESelectType.ToggleGroup)
                    mSelectable.Select();
                //if (grp != null && mSelectable != null)
                //grp.NotifySelected(mSelectable);
                return true;
            }
        }

        internal class SelectableGroup : SelectableBase
        {
            protected SelectableNavigation mGroup;
            protected List<SelectableBase> mSelectables;
            private bool mIsDirty;
            //protected int mWrapSides;
            protected bool mHasTog;
            SelectableBase mFinalSelected;
            bool mSelectingNext;
            readonly int id;

            public override int InstanceId => id;

            public SelectableGroup(SelectableNavigation group) : base(group.transform as RectTransform)
            {
                id = group.GetInstanceID();
                mGroup = group;
                //mWrapSides = group.m_WrapSides;
                mIsDirty = true;
                if (mGroup != null && mGroup.m_LocateTransform != null)
                    mRect = mGroup.m_LocateTransform;
            }

            public override Rect GetLocalRect(Transform parent)
            {
                SelectableRect rect = default;
                if (mGroup.mSelectableHandler != null)
                    rect = mGroup.mSelectableHandler.GetSelectableRect(mRect.gameObject, parent);
                if (rect.isVaild)
                    return rect.rect;
                else
                    return SelectableNavigation.GetLocalRect(parent, mRect, mRect.rect);
            }

            public void SetDirty()
            {
                if (mGroup != null && mGroup.m_LocateTransform != null)
                    mRect = mGroup.m_LocateTransform;
                mIsDirty = true;
            }

            public override bool IsSelected(SelectableNavigation grp, RectTransform current)
            {
                LoadSelectableTargets();
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (sel.IsSelected(mGroup, current))
                        return true;
                }
                return false;
            }

            public override bool IsToggleOn()
            {
                LoadSelectableTargets();
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (sel.IsToggleOn())
                        return true;
                }
                return false;
            }

            public override bool IsSelectable(SelectableNavigation grp)
            {
                LoadSelectableTargets();
                return mSelectables.Count > 0 && mGroup != null && mGroup.isActiveAndEnabled;
            }

            internal void ResetBetweenGroup()
            {
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var grp = mSelectables[i] as SelectableGroup;
                    if (grp != null)
                        grp.SetFinalSelected(null);
                }
            }

            internal void GetFinalSelected()
            {
                var ev = EventSystem.current;
                var sel = ev == null ? null : ev.currentSelectedGameObject;
                if (mGroup == null || sel == null || !sel.transform.IsChildOf(mGroup.transform))
                    return;
                LoadSelectableTargets();
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var item = mSelectables[i];
                    if (item.rectTransform == sel.transform)
                    {
                        if (item.IsSelectable(mGroup))
                            mFinalSelected = item;
                        return;
                    }
                }
            }

            internal void SetFinalSelected(GameObject target)
            {
                if (target == null)
                {
                    if (mFinalSelected is SelectableGroup)
                        ((SelectableGroup)mFinalSelected).SetFinalSelected(null);
                    mFinalSelected = null;
                    return;
                }
                LoadSelectableTargets();
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var item = mSelectables[i];
                    if (item.rectTransform == target.transform)
                    {
                        if (item.IsSelectable(mGroup))
                            mFinalSelected = item;
                        return;
                    }
                }
            }

            internal GameObject GetSelected(RectTransform current)
            {
                LoadSelectableTargets();
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var item = mSelectables[i];
                    GameObject sel;
                    if (item is SelectableGroup)
                        sel = ((SelectableGroup)item).GetSelected(current);
                    else if (item.IsSelected(mGroup, current))
                        return item.rectTransform.gameObject;
                    else
                        sel = null;
                    if (sel != null)
                        return sel;
                }
                return null;
            }

            internal GameObject GetToggleOn()
            {
                LoadSelectableTargets();
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var item = mSelectables[i];
                    GameObject sel;
                    if (item is SelectableGroup)
                        sel = ((SelectableGroup)item).GetToggleOn();
                    else if (item.IsToggleOn())
                        return item.rectTransform.gameObject;
                    else
                        sel = null;
                    if (sel != null)
                        return sel;
                }
                return null;
            }

            protected virtual int GetSelectedIndex(RectTransform current, SelectableGroup skipGrp)
            {
                if (current == null || mSelectables == null)
                {
                    return -1;
                }
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (sel != skipGrp && sel.IsSelected(mGroup, current))
                    {
                        return i;
                    }
                }
                return -1;
            }

            SelectableBase GetDefaultSelectable()
            {
                if (mFinalSelected != null && mFinalSelected.IsSelectable(mGroup))
                    return mFinalSelected;
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (sel.IsSelectable(mGroup))
                        return sel;
                }
                return null;
            }

            public override bool Select(SelectableNavigation grp, RectTransform src)
            {
                LoadSelectableTargets();
                Rect srcRect;
                if (src == null)
                {
                    if (mHasTog && (mFinalSelected == null || !mFinalSelected.IsToggleOn()))
                    {
                        for (int i = 0; i < mSelectables.Count; i++)
                        {
                            var sel = mSelectables[i];
                            if (sel.IsSelectable(mGroup) && sel.IsToggleOn())
                            {
                                mFinalSelected = sel;
                                break;
                            }
                        }
                    }
                    if (mFinalSelected != null && mSelectables.Contains(mFinalSelected) && mFinalSelected.IsSelectable(mGroup))
                    {
                        return mFinalSelected.Select(mGroup, src);
                    }
                    srcRect = mRect.rect;
                    srcRect = new Rect(srcRect.x, srcRect.yMax, 1, 1);
                }
                else
                {
                    srcRect = SelectableNavigation.GetLocalRect(mRect, src, src.rect);
                }
                SelectableBase tosel = null;
                float dis = 0;
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (!sel.IsSelectable(mGroup))
                        continue;
                    if (src == sel.rectTransform)
                    {
                        tosel = sel;
                        break;
                    }
                    var len = CalDistance(sel, srcRect);
                    if (tosel == null || len < dis)
                    {
                        tosel = sel;
                        dis = len;
                    }
                }
                if (tosel != null)
                {
                    if (mSelectingNext && tosel.rectTransform == src)
                        return false;
                    mFinalSelected = tosel;
                    return tosel.Select(mGroup, src);
                }
                //}
                return false;
            }

            public bool SelectNext(RectTransform rect, MoveDirection dir)
            {
                if (dir == MoveDirection.None)
                    return false;
                if (rect != null && !rect.gameObject.activeInHierarchy)
                    rect = null;
                TrackSelectableBegin();

                bool result = false;
                if (sSelecting.Add(InstanceId))
                {
                    try
                    {

                        INavigateHandler selectHandler = null;
                        LoadSelectableTargets();
                        if (rect == null /*&& mHasTog*/)
                        {
                            if (mHasTog)
                            {
                                for (int i = 0; i < mSelectables.Count; i++)
                                {
                                    var sel = mSelectables[i];
                                    if (sel.IsSelected(mGroup, null))
                                    {
                                        rect = sel.rectTransform;
                                        break;
                                    }
                                }
                            }
                            else
                            {
                                var src = EventSystem.current.currentSelectedGameObject;
                                if (src != null)
                                    rect = src.transform as RectTransform;
                            }
                        }
                        if (rect != null)
                            rect.TryGetComponent(out selectHandler);
                        mSelectingNext = rect != null;
                        if (mSelectingNext && mGroup.mBlocker != null && !mGroup.mBlocker.CanMoveNext(mGroup, rect.gameObject, dir))
                        {
                            result = false;
                        }
                        else if (rect == null)
                        {
                            var sel = GetDefaultSelectable();
                            if (sel == null)
                            {
                                result = false;
                            }
                            else
                            {
                                mFinalSelected = sel;
                                if (sel is SelectableGroup)
                                    result = ((SelectableGroup)sel).SelectNext(rect, dir);
                                else
                                    result = sel.Select(mGroup, rect);
                                //result = Select(mGroup, null);
                            }
                        }
                        else if (selectHandler != null && selectHandler.OnMoveNext(dir))
                        {
                            result = true;
                        }
                        else if (dir == MoveDirection.Left)
                        {
                            result = SelectNextOnHorizontal(rect, -1, null);
                        }
                        else if (dir == MoveDirection.Right)
                        {
                            result = SelectNextOnHorizontal(rect, 1, null);
                        }
                        else if (dir == MoveDirection.Up)
                        {
                            result = SelectNextOnVertical(rect, 1, null);
                        }
                        else
                        {
                            result = SelectNextOnVertical(rect, -1, null);
                        }
                        if (mSelectingNext && !result && mGroup.m_FailedVibrate > 0)
                        {
                            GameInput.Vibrate(InputController.EVibrationID.Left, mGroup.m_FailedVibrate, 0.1f);
                        }
                        mSelectingNext = false;
                    }
                    catch (System.Exception e)
                    {
                        Debug.LogException(e);
                        result = false;
                    }
                }
                TrackSelectableEnd();
                return result;
            }

            float CalDistance(SelectableBase sel, Rect rect)
            {
                var area = sel.GetLocalRect(mRect);// GetLocalRect(sel.rectTransform);
                var dx = Mathf.Max(area.xMax, rect.xMax) - Mathf.Min(area.xMin, rect.xMin);
                dx = Mathf.Min(dx - rect.width, dx - area.width);

                var dy = Mathf.Max(area.yMax, rect.yMax) - Mathf.Min(area.yMin, rect.yMin);
                dy = Mathf.Min(dy - rect.height, dy - area.height);
                return dx + dy;

                //var delta = area.center - rect.center;
                //return delta.sqrMagnitude;
            }

            //float CalDistance(SelectableBase sel, RectTransform rect)
            //{
            //    var area = sel.Rect.rect;
            //    //var selrect = sel.Rect.rect.center;
            //    var matrix = sel.Rect.worldToLocalMatrix * rect.localToWorldMatrix;
            //    var min = matrix.MultiplyPoint(rect.rect.min);
            //    var max = matrix.MultiplyPoint(rect.rect.max);
            //    float dx = min.x >= area.xMax ? (min.x - area.xMax) : (max.x <= area.xMin ? area.xMin - max.x : 0);
            //    float dy = min.y >= area.yMax ? (min.y - area.yMax) : (max.y <= area.yMin ? area.yMin - max.y : 0);
            //    return dx + dy;
            //}

            float CalDistanceOnHorizontal(SelectableBase sel, Rect rect, int dx)
            {
                var selrect = sel.GetLocalRect(mRect);
                //var pos = rect.center;
                float dis;
                //var width = Mathf.Max(1, rect.width * mGroup.m_GridThreshold);
                if (selrect.xMin > rect.xMax || selrect.xMax < rect.xMin)
                    dis = Mathf.Min((selrect.xMin - rect.xMax) * dx, (selrect.xMax - rect.xMin) * dx);
                else
                    dis = Mathf.Min((selrect.xMax - rect.xMax) * dx, (selrect.xMin - rect.xMin) * dx) - Mathf.Max(1, rect.width * mGroup.m_GridThreshold);
                if (dis > 0 && rect.height > 1)
                {
                    var y1 = selrect.yMin - rect.yMin;
                    var y2 = selrect.yMax - rect.yMax;
                    if (y1 * y2 > 0)
                    {
                        dis += Mathf.Min(Mathf.Abs(y1), Mathf.Abs(y2)) * Mathf.Max(1f, mGroup.m_GridDistanceScale);
                    }
                }
                return dis;
            }

            float CalDistanceOnVertical(SelectableBase sel, Rect rect, int dy)
            {
                var selrect = sel.GetLocalRect(mRect);
                //var pos = rect.center;
                float dis;
                //var height = Mathf.Max(1, rect.height * mGroup.m_GridThreshold);
                if (selrect.yMin > rect.yMax || selrect.yMax < rect.yMin)
                    dis = Mathf.Min((selrect.yMin - rect.yMax) * dy, (selrect.yMax - rect.yMin) * dy);
                else
                    dis = Mathf.Min((selrect.yMin - rect.yMin) * dy, (selrect.yMax - rect.yMax) * dy) - Mathf.Max(1, rect.height * mGroup.m_GridThreshold);
                if (dis > 0 && rect.width > 1)
                {
                    var x1 = selrect.xMin - rect.xMin;
                    var x2 = selrect.xMax - rect.xMax;
                    if (x1 * x2 > 0)
                    {
                        dis += Mathf.Min(Mathf.Abs(x1), Mathf.Abs(x2)) * Mathf.Max(1f, mGroup.m_GridDistanceScale);
                    }
                }
                return dis;
            }

            internal virtual bool SelectNextOnHorizontal(RectTransform rect, int dx, SelectableGroup fromGrp)
            {
                LoadSelectableTargets();
                float dis = 0;
                SelectableBase tosel = null;

                sSelecting.Add(InstanceId);

                var srcRect = SelectableNavigation.GetLocalRect(mRect, rect, rect.rect);
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (sSelecting.Contains(sel.InstanceId) ||
                        sel == fromGrp || sel.rectTransform == rect || !sel.IsSelectable(mGroup))
                        continue;
                    var len = CalDistanceOnHorizontal(sel, srcRect, dx);
                    if (len > 0)
                    {
                        if (tosel == null || len < dis)
                        {
                            tosel = sel;
                            dis = len;
                        }
                    }
                }
                if (tosel != null)
                {
                    mFinalSelected = tosel;
                    if (fromGrp != null && mGroup.m_ReselectBetweenGroup)
                        return tosel.Select(mGroup, null);
                    else
                        return tosel.Select(mGroup, rect);
                }
                else
                {
                    var wrap = dx > 0 ? EWrapSide.Right : EWrapSide.Left;
                    if ((mGroup.m_WrapSides & (int)wrap) == 0)
                    {
                        var parent = GetParentGroup();
                        if (parent != null)
                            return parent.SelectNextOnHorizontal(rect, dx, this);
                        else
                            return Select(mGroup, rect);
                    }
                    else
                    {
                        return false;
                    }
                }
            }

            internal virtual bool SelectNextOnVertical(RectTransform rect, int dy, SelectableGroup fromGroup)
            {
                LoadSelectableTargets();
                float dis = 0;
                SelectableBase tosel = null;

                sSelecting.Add(InstanceId);

                var srcRect = SelectableNavigation.GetLocalRect(mRect, rect, rect.rect);// GetLocalRect(rect);
                for (int i = 0; i < mSelectables.Count; i++)
                {
                    var sel = mSelectables[i];
                    if (sSelecting.Contains(sel.InstanceId) ||
                        sel == fromGroup || sel.rectTransform == rect || !sel.IsSelectable(mGroup))
                        continue;
                    var len = CalDistanceOnVertical(sel, srcRect, dy);
                    if (len > 0)
                    {
                        if (tosel == null || len < dis)
                        {
                            tosel = sel;
                            dis = len;
                        }
                    }
                }
                if (tosel != null)
                {
                    mFinalSelected = tosel;
                    if (fromGroup != null && mGroup.m_ReselectBetweenGroup)
                        return tosel.Select(mGroup, null);
                    else
                        return tosel.Select(mGroup, rect);
                }
                else
                {
                    var wrap = dy > 0 ? EWrapSide.Up : EWrapSide.Down;
                    if ((mGroup.m_WrapSides & (int)wrap) == 0)
                    {
                        var parent = GetParentGroup();
                        if (parent != null)
                            return parent.SelectNextOnVertical(rect, dy, this);
                        else
                            return Select(mGroup, rect);
                    }
                    else
                    {
                        return false;
                    }
                }
            }


            protected SelectableGroup GetParentGroup()
            {
                var parent = mGroup.transform.parent;
                var pgrp = parent == null ? null : parent.GetComponentInParent<SelectableNavigation>();
                return pgrp == null ? null : pgrp.GetSelectableGroup();
            }

            protected void LoadSelectableTargets()
            {
                if (!mIsDirty)
                    return;
                mIsDirty = false;
                if (mSelectables == null)
                    mSelectables = new List<SelectableBase>();
                else
                    mSelectables.Clear();
                var isTog = mGroup.m_SelectType == ESelectType.ToggleGroup;
                mHasTog = false;

                sCache.Clear();
                sCache.Enqueue(mGroup.transform);
                while (sCache.Count > 0)
                {
                    var root = sCache.Dequeue();
                    var size = root.childCount;
                    for (int i = 0; i < size; i++)
                    {
                        var child = root.GetChild(i);

                        var grp = child.GetComponent<SelectableNavigation>();
                        if (grp != null)
                        {
                            var data = grp.GetSelectableGroup();
                            if (data != null)
                                mSelectables.Add(data);
                            continue;
                        }

                        var sel = child.GetComponent<Selectable>();
                        if (sel != null)
                        {
                            var tog = (sel is IToggle) || (sel is Toggle);
                            if (!isTog || tog)
                            {
                                mSelectables.Add(new SelectableTarget(sel));
                                mHasTog |= tog;
                            }
                            continue;
                        }

                        sCache.Enqueue(child);
                    }

                }
            }

            public IList<SelectableBase> Selectables
            {
                get
                {
                    LoadSelectableTargets();
                    return mSelectables;
                }
            }
        }

        class HorizontalGroup : SelectableGroup
        {
            public HorizontalGroup(SelectableNavigation grp) : base(grp) { }

            internal override bool SelectNextOnVertical(RectTransform rect, int dy, SelectableGroup fromGroup)
            {
                var wrap = dy > 0 ? EWrapSide.Up : EWrapSide.Down;
                if ((mGroup.m_WrapSides & (int)wrap) == 0)
                {
                    var parent = GetParentGroup();
                    if (parent != null)
                        return parent.SelectNextOnVertical(rect, dy, this);
                    else
                        return Select(mGroup, rect);
                }
                else
                {
                    return false;
                }
            }

        }

        class VerticalGroup : SelectableGroup
        {
            public VerticalGroup(SelectableNavigation grp) : base(grp) { }

            internal override bool SelectNextOnHorizontal(RectTransform rect, int dx, SelectableGroup fromGroup)
            {
                var wrap = dx > 0 ? EWrapSide.Right : EWrapSide.Left;
                if ((mGroup.m_WrapSides & (int)wrap) == 0)
                {
                    var parent = GetParentGroup();
                    if (parent != null)
                        return parent.SelectNextOnHorizontal(rect, dx, this);
                    else
                        return Select(mGroup, rect);
                }
                else
                {
                    return false;
                }
            }
        }
    }
}